#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
compute_loss_rate.py
功能：读取 _CLIENT_LOG（CSV），根据第 5 列 total_ms 是否为数字来统计丢包率。
规则：
  - 跳过第一行表头；
  - 对后续所有非空行，若提供 --frame_id_lt，则仅统计 frame_id(第一列，整数) < 该阈值 的行；
  - 第 5 列 total_ms 可解析为数字 → 该帧未丢；否则（NA/非数字）→ 该帧丢失；
输出：总帧数、收到帧数、丢失帧数、丢包率（百分比，四位小数）。

用法示例：
    python compute_loss_rate.py --path /path/to/net_webrtc_xxx.csv
    python compute_loss_rate.py --path /path/to/net_webrtc_xxx.csv --frame_id_lt 6000
"""

import argparse
import csv
import os
import sys
from typing import Optional

def _is_number_like(s: str) -> bool:
    """
    判断字符串 s 是否是“数字”。
    说明：
      - 返回 True：可以被 float() 成功解析（如 '12', '3.14', '0', '1e-3' 等）
      - 返回 False：'NA'、空串、包含非数字字符且无法解析的情况
    """
    if s is None:
        return False
    s = s.strip()
    if not s or s.upper() == "NA":
        return False
    try:
        float(s)
        return True
    except Exception:
        return False

def compute_loss_rate(csv_path: str, frame_id_lt: Optional[int] = None) -> None:
    """
    读取 csv_path，对除表头外的每一行（非空）进行统计：
      - 若提供 frame_id_lt：仅统计第一列 frame_id < frame_id_lt 的行（严格小于）；
      - 第 5 列 total_ms 可解析为数字 → 该帧未丢
      - 否则（NA/其他非数字） → 该帧丢失
    打印统计结果与丢包率（百分比，保留 4 位小数）。

    参数：
      - csv_path: 日志文件路径
      - frame_id_lt: （可选）帧编号上界（不含），仅统计 frame_id < 该值 的行
    """
    # === 新变量：计数器（新增变量，均在此处注释含义） ===
    total_frames = 0        # 含义：有效统计的帧总数（经过滤后）
    lost_frames = 0         # 含义：丢失的帧数量（total_ms 非数字或 NA）
    received_frames = 0     # 含义：收到 ACK 的帧数量（total_ms 为数字）

    # === 参数与路径检查 ===
    if not os.path.isfile(csv_path):
        print(f"[ERROR] File not found: {csv_path}", file=sys.stderr)
        sys.exit(1)

    # === 读取并统计 ===
    with open(csv_path, "r", newline="") as f:
        reader = csv.reader(f)
        try:
            # 跳过第一行表头
            header = next(reader, None)
        except Exception:
            header = None

        for row in reader:
            # 跳过空行或长度不足的行
            if not row or all((c.strip() == "" for c in row)):
                continue

            # === 新增代码块：帧编号过滤（基于第一列 frame_id） ===
            # 功能：当提供 --frame_id_lt 时，只统计 frame_id < frame_id_lt 的行
            try:
                frame_id_str = row[0].strip()
                frame_id = int(frame_id_str)
            except Exception:
                # 无法解析 frame_id 的行直接跳过（不参与统计）
                continue

            if frame_id_lt is not None and frame_id >= frame_id_lt:
                # 超出统计上界的行直接跳过
                continue
            # === 新增代码块结束 ===

            # 确保有第 5 列（total_ms）
            if len(row) < 5:
                # 第 5 列不存在，无法统计，按“丢失”处理更稳妥
                total_frames += 1
                lost_frames += 1
                continue

            total_ms_str = row[4].strip()

            total_frames += 1
            if _is_number_like(total_ms_str):
                received_frames += 1
            else:
                lost_frames += 1

    # === 输出结果 ===
    if total_frames == 0:
        # 当过滤过后没有任何有效行
        rng_desc = f"< {frame_id_lt}" if frame_id_lt is not None else "ALL"
        print(f"[WARN] No valid data rows found for range frame_id {rng_desc}. Loss rate is undefined.")
        return

    loss_rate = lost_frames / total_frames * 100.0
    rng_desc = f"< {frame_id_lt}" if frame_id_lt is not None else "ALL"
    print("==== Loss Statistics ====")
    print(f"File              : {csv_path}")
    print(f"Frame Range       : frame_id {rng_desc}")
    print(f"Total Frames      : {total_frames}")
    print(f"Received Frames   : {received_frames}")
    print(f"Lost Frames       : {lost_frames}")
    print(f"Loss Rate         : {loss_rate:.4f}%")

def main() -> None:
    parser = argparse.ArgumentParser(description="Compute packet loss rate from _CLIENT_LOG CSV.")
    parser.add_argument("--path", required=True, help="Path to _CLIENT_LOG CSV file")
    # === 新增参数：--frame_id_lt ===
    # 功能：只统计第一列 frame_id 小于该值的行；不提供则统计全量
    parser.add_argument("--frame_id_lt", type=int, default=6000,
                        help="Only count rows where frame_id (1st col) is less than this value (exclusive).")
    args = parser.parse_args()
    compute_loss_rate(args.path, frame_id_lt=args.frame_id_lt)

if __name__ == "__main__":
    main()
